/*
 * Copyright 2019 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "modules/skottie/src/effects/Effects.h"

#include "modules/skottie/src/SkottieValue.h"
#include "modules/sksg/include/SkSGGradient.h"
#include "modules/sksg/include/SkSGRenderEffect.h"
#include "src/utils/SkJSON.h"

namespace skottie {
namespace internal {
namespace {
class GradientRampEffectAdapter final : public AnimatablePropertyContainer {
public:
    static sk_sp<GradientRampEffectAdapter> Make(const skjson::ArrayValue &jprops, sk_sp<sksg::RenderNode> layer,
        const AnimationBuilder *abuilder)
    {
        return sk_sp<GradientRampEffectAdapter>(new GradientRampEffectAdapter(jprops, std::move(layer), abuilder));
    }

    sk_sp<sksg::RenderNode> node() const
    {
        return fShaderEffect;
    }

private:
    GradientRampEffectAdapter(const skjson::ArrayValue &jprops, sk_sp<sksg::RenderNode> layer,
        const AnimationBuilder *abuilder)
        : fShaderEffect(sksg::ShaderEffect::Make(std::move(layer)))
    {
        enum : size_t {
            kStartPoint_Index = 0,
            kStartColor_Index = 1,
            kEndPoint_Index = 2,
            kEndColor_Index = 3,
            kRampShape_Index = 4,
            kRampScatter_Index = 5,
            kBlendRatio_Index = 6,
        };

        EffectBinder(jprops, *abuilder, this)
            .bind(kStartPoint_Index, fStartPoint)
            .bind(kStartColor_Index, fStartColor)
            .bind(kEndPoint_Index, fEndPoint)
            .bind(kEndColor_Index, fEndColor)
            .bind(kRampShape_Index, fShape)
            .bind(kRampScatter_Index, fScatter)
            .bind(kBlendRatio_Index, fBlend);
    }

    enum class InstanceType {
        kNone,
        kLinear,
        kRadial,
    };

    void onSync() override
    {
        // This adapter manages a SG fragment with the following structure:
        //
        // - ShaderEffect [fRoot]
        //     \  GradientShader [fGradient]
        //     \  child/wrapped fragment
        //
        // The gradient shader is updated based on the (animatable) instance type (linear/radial).

        auto update_gradient = [this](InstanceType new_type) {
            if (new_type != fInstanceType) {
                fGradient = new_type == InstanceType::kLinear ? sk_sp<sksg::Gradient>(sksg::LinearGradient::Make()) :
                                                                sk_sp<sksg::Gradient>(sksg::RadialGradient::Make());

                fShaderEffect->setShader(fGradient);
                fInstanceType = new_type;
            }

            fGradient->setColorStops({ { 0, fStartColor }, { 1, fEndColor } });
        };

        static constexpr int kLinearShapeValue = 1;
        const auto instance_type =
            (SkScalarRoundToInt(fShape) == kLinearShapeValue) ? InstanceType::kLinear : InstanceType::kRadial;

        // Sync the gradient shader instance if needed.
        update_gradient(instance_type);

        // Sync instance-dependent gradient params.
        const auto start_point = SkPoint{ fStartPoint.x, fStartPoint.y },
                   end_point = SkPoint{ fEndPoint.x, fEndPoint.y };
        if (instance_type == InstanceType::kLinear) {
            auto *lg = static_cast<sksg::LinearGradient *>(fGradient.get());
            lg->setStartPoint(start_point);
            lg->setEndPoint(end_point);
        } else {
            SkASSERT(instance_type == InstanceType::kRadial);

            auto *rg = static_cast<sksg::RadialGradient *>(fGradient.get());
            rg->setStartCenter(start_point);
            rg->setEndCenter(start_point);
            rg->setEndRadius(SkPoint::Distance(start_point, end_point));
        }

        // TODO: blend, scatter
    }

    const sk_sp<sksg::ShaderEffect> fShaderEffect;
    sk_sp<sksg::Gradient> fGradient;

    InstanceType fInstanceType = InstanceType::kNone;

    ColorValue fStartColor, fEndColor;
    Vec2Value fStartPoint = { 0, 0 }, fEndPoint = { 0, 0 };
    ScalarValue fBlend = 0, fScatter = 0,
                fShape = 0; // 1 -> linear, 7 -> radial (?!)
};
} // namespace

sk_sp<sksg::RenderNode> EffectBuilder::attachGradientEffect(const skjson::ArrayValue &jprops,
    sk_sp<sksg::RenderNode> layer) const
{
    return fBuilder->attachDiscardableAdapter<GradientRampEffectAdapter>(jprops, std::move(layer), fBuilder);
}
} // namespace internal
} // namespace skottie
